package benchmark;

import java.util.Arrays;
import java.util.List;

public class ArrayRead implements ITask {

	private static final int LONG_SIZE = 8;
	
	private static int L1_SIZE;
	private static int L2_SIZE;
	private static int L3_SIZE;
	
	private static int WORD_PER_L1;
	private static int WORD_PER_L2;
	private static int WORD_PER_L3;
	
	
	private static int MEMORY_SIZE;
	private static int ARRAY_SIZE;
	
	private StrideType strideType;
	
	private static final int KB = 1024;
	private static final int MB = 1024 * KB;	
	private static final int GB = 1024 * MB;
	
//	private static final int PAGE_SIZE = 2 * 1024 * 1024;
//	private static final int ONE_GIG = 1024 * 1024 * 1024;
//	private static final long TWO_GIG = 2L * ONE_GIG;
	 
//	private static final int WORDS_PER_PAGE = PAGE_SIZE / LONG_SIZE;
	 
	 
	private static final int PRIME_INC = 514229;


	long[] array;

	public enum StrideType {
		LINEAR_WALK {
			public int next(int L2Offset, int L1Offset, int wordOffset, final int pos)
			{
				return (pos + 1) % ARRAY_SIZE;
			}
		},
 
		RANDOM_L1_WALK {
			public int next(int L2Offset, int L1Offset, int wordOffset, final int pos)
			{
				return L1Offset + (pos + PRIME_INC) % WORD_PER_L1;
			}
		},
 
		RANDOM_L2_WALK {
			public int next(int L2Offset, int L1Offset, int wordOffset, final int pos)
			{
				return L2Offset + (pos + PRIME_INC) % WORD_PER_L2;
			}
		},

		RANDOM_L3_WALK {
			public int next(int L2Offset, int L1Offset, int wordOffset, final int pos)
			{
				return (pos + PRIME_INC) % WORD_PER_L3;
			}
		};
 
		public abstract int next(int L2Offset, int L1Offset, int wordOffset, int pos);
		
	}
	
	/*
	 * 0. class name
	 * 1. L1 size
	 * 2. L2 size
	 * 3. L3 size
	 * 4. working set size
	 * 5. access mode
	 * 	0. linear walk
	 * 	1. random walk in L1
	 * 	2. random walk in L2
	 * 	3. random walk in L3
	 * @see benchmark.ITask#init(java.util.List)
	 */
	@Override
	public void init(List<String> param) {
		L1_SIZE = Integer.parseInt(param.get(1));
		WORD_PER_L1 = L1_SIZE / LONG_SIZE;
		L2_SIZE = Integer.parseInt(param.get(2));
		WORD_PER_L2 = L2_SIZE / LONG_SIZE;
		L3_SIZE = Integer.parseInt(param.get(3));
		WORD_PER_L3 = L3_SIZE / LONG_SIZE;
		MEMORY_SIZE = Integer.parseInt(param.get(4));
		ARRAY_SIZE = MEMORY_SIZE / LONG_SIZE;
		
		switch (Integer.parseInt(param.get(5))) {
		case 0:
			strideType = StrideType.LINEAR_WALK;
			break;
		case 1:
			strideType = StrideType.RANDOM_L1_WALK;
			break;
		case 2:
			strideType = StrideType.RANDOM_L2_WALK;
			break;
		case 3:
			strideType = StrideType.RANDOM_L3_WALK;
			break;
		}

		array = new long[ARRAY_SIZE];
		
		for (int i = 0; i < ARRAY_SIZE; i++) {
//			array[i] = (int) (System.nanoTime() % ARRAY_SIZE) + i;
			array[i] = 777;
		}

	}

	@Override
	public String runTask() {
		long sum = 0;
		int pos = -1;
		
		String msg = "";
		
//		for (int wordOffset = 0; wordOffset < ARRAY_SIZE; wordOffset++) {
////			pos = strideType.next(0, 0, 0, pos);
//			pos++;
//			if(pos >= ARRAY_SIZE)
//				continue;
//			sum += array[pos];
//		}

		
		for (int L3Offset = 0; L3Offset < ARRAY_SIZE; L3Offset += WORD_PER_L3) {
			for (int L2Offset = L3Offset, limitL2 = L3Offset + WORD_PER_L3; L2Offset < limitL2; L2Offset += WORD_PER_L2) {
				for (int L1Offset = L2Offset, limitL1 = L2Offset + WORD_PER_L2; L1Offset < limitL1; L1Offset += WORD_PER_L1) {
					for (int wordOffset = L1Offset, limitWord = L1Offset + WORD_PER_L1; wordOffset < limitWord; wordOffset++) {
						pos = strideType.next(L2Offset, L1Offset, wordOffset, pos);
						if(pos >= ARRAY_SIZE)
							continue;
						sum += array[pos];
//						msg += Integer.toString(pos) + ", ";
					}
//					msg += "\n";
				}
//				msg += "\n";
			}
//			msg += "------------------------------------------\n";		
		}
		
//		System.out.println(msg);
//		for(int i = 0; i < ARRAY_SIZE; i++) {
//			pos = strideType.next(pos);
//			sum += array[pos];
//		}
		return Long.toString(sum);
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String[] param = {"ArrayRead", 
				Integer.toString(64), 
				Integer.toString(256), 
				Integer.toString(512), 
				Integer.toString(1024 + 8), 
				"2"};
		ArrayRead task = new ArrayRead();
		task.init(Arrays.asList(param));
		task.runTask();

	}

}
